/*
 * Cally.cpp
 *
 *  Created on: Jul 7, 2009
 *      Author: lfor
 */

#include "Cally.h"

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>

// Threading

// statics
Cally * Cally::Instance = NULL;
int Cally::ThreadLoop = 1;
CallyVectorElement Cally::CallyFunctionVector[MAX_FUNC_NUM];
int Cally::ElementNumber = 0;

// Functions
Cally::Cally() : Initialized(0)
{

}

Cally * Cally::GetInstance()
{
	if (Instance == NULL)
	{
		Instance = new Cally();
	}

	return(Instance);
}

int Cally::Initialize(int PortNum)
{
	if (!Initialized)
	{
		// Argument parsing
		if (PortNum == 0)
		{
			PortNum = DEFAULT_PORT;
		}

		// Start cally thread
		if (pthread_create(&CallyThreadId, NULL, CallyThread, (void *) &PortNum) != 0)
		{
			return(CALLY_THREAD_CREATE_ERROR);
		}

		Initialized = 1;
	}

    return(CALLY_OK);
}

int Cally::Finalize()
{
	if (Initialized)
	{
		ThreadLoop = 0;

		Initialized = 0;
	}

	return(CALLY_OK);
}

int Cally::RegisterFuntion(CallyFunction Func, char * Name, char * Help)
{
	CallyVectorElement NewElement;

	NewElement.Function = Func;

	int Len = strlen(Name);
	if (Len >= MAX_NAME_SIZE)
	{
		return(CALLY_NAME_TOO_LONG);
	}

	strncpy(NewElement.Name, Name, Len);
	NewElement.Name[Len] = 0; // NULL termination

	Len = strlen(Help);
	if (Len >= MAX_PARAM_SIZE)
	{
		Len = MAX_PARAM_SIZE-1;
	}

	strncpy(NewElement.Help, Help, Len);
	NewElement.Help[Len] = 0; // NULL termination

	if (ElementNumber < MAX_FUNC_NUM)
	{
		CallyFunctionVector[ElementNumber] = NewElement;
		ElementNumber++;
	}
	else
	{
		return(CALLY_ERROR);
	}

	return(CALLY_OK);
}

int Cally::List(char * Buffer, int Length)
{
	int Size = ElementNumber;
	int i = 0, Index = 0;

	if ((Buffer == NULL))
	{
		return(CALLY_ERROR);
	}

	for (i=0; i<Size; i++)
	{
		int Len = strlen(((CallyVectorElement) CallyFunctionVector[i]).Name);
		if (Len < Length-Index)
		{
			strncpy(Buffer+Index, ((CallyVectorElement) CallyFunctionVector[i]).Name, Len);
			Index += Len;
			Buffer[Index] = ':';
			Index++;
			Buffer[Index] = ' ';
			Index++;
		}
		else
		{
			break;
		}

		Len = strlen(((CallyVectorElement) CallyFunctionVector[i]).Help);
		if (Len < Length-Index)
		{
			strncpy(Buffer+Index, ((CallyVectorElement) CallyFunctionVector[i]).Help, Len);
			Index += Len;
			Buffer[Index] = '\n';
			Index++;
		}
		else
		{
			break;
		}
	}

	Buffer[Index] = 0;

	return(CALLY_OK);
}



int Cally::Call(StatusType * Status, char * Name, char * Params, char * Returns, char * Errors, int DataLength, char * Data)
{
	int Size = ElementNumber;
	int i = 0;
	int Ret = CALLY_OK;

	if ((Status == NULL) || (Name == NULL) || (Params == NULL) || (Returns == NULL) || (Errors == NULL))
	{
		return(CALLY_ERROR);
	}

	// Parse the vector
	for (i=0; i<Size; i++)
	{
		char FirstChar = ((CallyVectorElement) CallyFunctionVector[i]).Name[0];
		if (FirstChar != Name[0])
		{
			continue;
		}

		if (strcmp(((CallyVectorElement) CallyFunctionVector[i]).Name, Name) == 0)
		{
			break;
		}
	}

	if (i < Size)
	{
		*Status = OK;
		strncpy(Returns, ((CallyVectorElement) CallyFunctionVector[i]).Function(Params), MAX_RETURN_SIZE);
	}
	else
	{
		*Status = ERROR;
		snprintf(Errors, MAX_ERROR_SIZE, "Function \"%s\" not in the list!", Name);
	}

	return(Ret);
}


void * Cally::CallyThread(void * Arg)
{
	int Error = 0;

	sockaddr_in CallyAddr;
    int ListenSock = 0;
    int PortNum = *((int *) Arg);

	sockaddr_in CallerAddr;
	int CallerSock = 0;

    char Buffer[MAX_BUFF_SIZE];

    // Create main socket
	ListenSock = socket(AF_INET, SOCK_STREAM, 0);
    if (ListenSock < 0)
    {
    	Error = errno;
    	pthread_exit(NULL);
    }

    unsigned int opt = 1;
	if (setsockopt(ListenSock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1)
	{
    	Error = errno;
	   	pthread_exit(NULL);
	}

    // Bind
    bzero((char *) &CallyAddr, sizeof(CallyAddr));
    CallyAddr.sin_family = AF_INET;
    CallyAddr.sin_addr.s_addr = INADDR_ANY;
    CallyAddr.sin_port = htons(PortNum);
    if (bind(ListenSock, (struct sockaddr *) &CallyAddr, sizeof(CallyAddr)) < 0)
    {
    	Error = errno;
    	pthread_exit(NULL);
    }

    // Listen
    if(listen(ListenSock, 1) < 0)
    {
    	Error = errno;
    	pthread_exit(NULL);
    }

    while(ThreadLoop)
	{
	    printf("Waiting connections on Port %d...\n", PortNum);

		// Accept
		int TmpLen = sizeof(CallerAddr);
		CallerSock = accept(ListenSock, (struct sockaddr *) &CallerAddr, (socklen_t*)&TmpLen);
		if (CallerSock < 0)
		{
	    	Error = errno;
			pthread_exit(NULL);
		}

	    printf("Connection accepted!\n");

		// Process communication
	    int RecvCount = 1;
		while (RecvCount > 0)
		{
		    bzero(Buffer,MAX_BUFF_SIZE);
		    RecvCount = recv(CallerSock, Buffer, MAX_BUFF_SIZE, 0);
		    if (RecvCount > 0)
		    {
		    	//printf("CALLY RECV: \n%s\n",Buffer);

		    	PacketType RecvPacket;
		    	if (ParseBuffer(Buffer, RecvCount, &RecvPacket) == PACKET_OK)
		    	{
					PacketType ReturnPacket;
				    bzero(Buffer,MAX_BUFF_SIZE);
				    int BuffSize = MAX_BUFF_SIZE;
				    char DataBuff[MAX_BUFF_SIZE];

		    		switch (RecvPacket.Status)
		    		{
		    		case CALL:
						Instance->Call(&ReturnPacket.Status, RecvPacket.Function, RecvPacket.Params, ReturnPacket.Returns, ReturnPacket.Errors, RecvPacket.DataLength, RecvPacket.Data);
		    			strcpy(ReturnPacket.Function, RecvPacket.Function);
		    			strcpy(ReturnPacket.Params, RecvPacket.Params);
						ReturnPacket.DataLength = 0;
						ReturnPacket.Data = NULL;
		    			break;

		    		case LIST:
						Instance->List(DataBuff, MAX_BUFF_SIZE);
						ReturnPacket.Status = LIST;
						ReturnPacket.Function[0] = 0;
						ReturnPacket.Params[0] = 0;
						ReturnPacket.Returns[0] = 0;
						ReturnPacket.Errors[0] = 0;
						ReturnPacket.DataLength = strlen(DataBuff);
						ReturnPacket.Data = DataBuff;
		    			break;

		    		case CLOSE:
		    			ThreadLoop = 0;
		    			RecvCount = 0;
						ReturnPacket.Status = CLOSE;
						ReturnPacket.Function[0] = 0;
						ReturnPacket.Params[0] = 0;
						ReturnPacket.Returns[0] = 0;
						ReturnPacket.Errors[0] = 0;
						ReturnPacket.DataLength = 0;
						ReturnPacket.Data = NULL;
		    			break;

		    		default:
						ReturnPacket.Status = NONE;
		    			break;
		    		}

		    		// Send return packet
		    		if (FillBuffer(ReturnPacket, Buffer, &BuffSize) == PACKET_OK)
		    		{
		    	    	//printf("CALLY SEND: \n%s\n",Buffer);

		    		    send(CallerSock, Buffer, BuffSize, 0);
		    		}
		    	}
		    }
		}

		shutdown(CallerSock, SHUT_RDWR);

	    printf("Connection closed!\n");
	}

	shutdown(ListenSock, SHUT_RDWR);

	pthread_exit(NULL);
}

